




namespace Orz.LogManager
{
	//========================================================================
	// 	Imported Namespaces
	//========================================================================
	using System;
	using System.IO;
	using System.Text;
	using System.Text.RegularExpressions;
	using Orz.Net.Telnet;
	using Orz.LogManager.Config;
	using System.Diagnostics;
	
	
	
	///
	/// <summary>
	/// Filters and colour-codes lines of text.
	/// </summary>
	/// <remarks>
	/// This class can be set up to search for a specific pattern in given 
	/// lines of text and to choose a colour based on a match, perform a substitution,
	/// or both.
	/// </remarks>
	///
	public class TelnetFileStream : ILogManagerStream
	{
		//================================================================
		// 	Private Data Members
		//================================================================
		
		/// <summary>The main telnet connection object</summary>
		private TelnetConnection connection_i;
		
		/// <summary>The auto-login commands and expected responses</summary>
		private logsetLogfileAutoLogin[] autoLoginDetails_i;
		
		/// <summary>The command to execute once the 'auto login' has completed</summary>
		private string sTelnetCommand_i;
		
		/// <summary>The path of the logfile on the remote system</summary>
		private string sPath_i;
		
		/// <summary><code>true</code> if the 'auto login' is done and the main command has been executed</summary>
		private bool bExecuting_i = false;
		
		/// <summary>The main telnet connection object</summary>
		private StringBuilder buffer_i = new StringBuilder();
		
		/// <summary>The next auto-login command to execute</summary>
		private int iNextAutoLoginIndex_i = 0;
		
		/// <summary>The pattern of the next auto-login command</summary>
		private bool bWroteFirstAutoLoginPrompt_i = false;
		
		/// <summary>Unix request to kill the current remote tail process</summary>
		private const char CTRL_C = (char)3;
		
		/// <summary>Command to print the entire file in unix</summary>
		private const string CAT_COMMAND = "cat ";
		
		/// <summary>Holds text received since the last time we sent an autologin cmd</summary>
		private string sBufferSinceLastAutoLogin_i = "";
		
		/// <summary>String representation of this stream, based on host etc.</summary>
		private string sDebugIdentifier_i;
		
		/// <summary>Keeps track of the number of connections that have been opened</summary>
		private static int iConnectionCounter_c = 1;
		
		
		
		
		//================================================================
		// 	Constructors
		//================================================================

		///
		/// <summary>
		/// Stores the input parameters and creates a new telnet connection.
		/// </summary>
		///
		public TelnetFileStream (string sHost_p, int iPort_p, string sTelnetCommand_p, string sPath_p, logsetLogfileAutoLogin[] autoLoginDetails_p)
		{
			sTelnetCommand_i = sTelnetCommand_p;
			sPath_i = sPath_p;
			autoLoginDetails_i = autoLoginDetails_p;
			
			sDebugIdentifier_i = CreateDebugIdentifier(sHost_p);
			
			LogDebug("Connecting to " + sHost_p + " with path " + sPath_p);
			
			buffer_i.Append ("Connecting...\n");
			
			if (0 == iPort_p)
			{			
				connection_i = new TelnetConnection (sHost_p);			
			}
			else
			{
				connection_i = new TelnetConnection (sHost_p, iPort_p);			
			}
			
			connection_i.TextReceived += new TelnetConnection.TextReceivedEvent (OnTextReceived);
			connection_i.Connect ();			    	
		}
		
		
		
		
	
		//================================================================
		// 	Methods
		//================================================================
		
		///
		/// <summary>
		/// Closes the telnet connection.
		/// </summary>
		///
		public void Close ()
		{
			LogDebug("Closing connection.");
			connection_i.Close();
			connection_i = null;
		}
		
		
		
		
		///
		/// <summary>
		/// Flushes the output buffer and returns its contents.
		/// </summary>
		///
		public string ReadToEnd ()
		{
			if (! bExecuting_i)  return "";
	
			StringBuilder bufferCopy = buffer_i;
			buffer_i = new StringBuilder();
			
			string sTextToReturn = bufferCopy.ToString();
			
			return sTextToReturn;
		}
		
		
		
		///
		/// <summary>
		/// Return to the start of the file.
		/// </summary>
		/// <remarks>
		/// Sends a Ctrl+C to the tail process, followed by a 'cat' of the file,
		/// and then starts the 'tail' up again.
		/// </remarks>		
		///
		public void SeekToStart()
		{			 
			LogDebug("Seeking to start - will lose " + buffer_i.Length + " chars");
			LogDebug("CtrlC = <" + CTRL_C + "> and string len = " + CTRL_C.ToString().Length);
			
			buffer_i = new StringBuilder();			
			connection_i.Send (CTRL_C.ToString());
			
			// If we don't pause here then the Ctrl+C won't be processed
			// before the "cat" command is sent.
			System.Threading.Thread.Sleep(2000);
			
			connection_i.Send (CAT_COMMAND + sPath_i + "\n");
			connection_i.Send (sTelnetCommand_i + sPath_i + "\n");
		}
		
		
		
		///
		/// <summary>
		/// Clears the contents of the buffer.
		/// </summary>
		///
		public void SeekToEnd()
		{
			LogDebug("Seeking to end - will lose " + buffer_i.Length + " chars");
			buffer_i = new StringBuilder();
		}
		
		
		
		
		
			
		///
		/// <summary>
		/// Handles text being returned from the telnet connection.
		/// </summary>
		/// <remarks>
		/// This method is called whenever text is sent from the remote connection.
		/// If there are still auto-login commands to be processed, these will
		/// be sent; otherwise, if the main tail command has not yet been run, it will be run
		/// now.  If the tail command has already been started, the text will
		/// be appended to the output buffer.
		/// </remarks>		
		///
		private void OnTextReceived (object sender, TextReceivedEventArgs e)
		{
			LogDebug("Received " + e.Text.Length + " bytes");
			
			if (! bExecuting_i)
			{
				if (iNextAutoLoginIndex_i < autoLoginDetails_i.Length)
				{
					sBufferSinceLastAutoLogin_i += e.Text;
					
					// Still more autologin commands to execute
					LogDebug("Still another " + (autoLoginDetails_i.Length - iNextAutoLoginIndex_i) + " autologin events to process..");
										
					logsetLogfileAutoLogin autoLogin = autoLoginDetails_i[iNextAutoLoginIndex_i];
					
					if (0 == iNextAutoLoginIndex_i  &&  !bWroteFirstAutoLoginPrompt_i)
					{
						LogDebug("This is the first autologin prompt - waiting for pattern <" + autoLogin.pattern + ">");
						System.Threading.Thread.Sleep(2000);
						buffer_i.Append ("Waiting for AutoLogin prompt: " + autoLogin.pattern + "...");	
						bWroteFirstAutoLoginPrompt_i = true;
					}
										
					Regex oRegex = new Regex (autoLogin.pattern, RegexOptions.IgnoreCase | RegexOptions.Multiline);
					
					if (oRegex.IsMatch(sBufferSinceLastAutoLogin_i))
					{
						LogDebug("Found a match for autologin string <" + autoLogin.pattern + ">");
						
						// This is one of the lines we've been expecting -
						// send the response string
						iNextAutoLoginIndex_i++;
						
						if (autoLogin.send.encrypted)
						{
							connection_i.Send (PasswordHelper.Decrypt(autoLogin.send.Value) + "\n");
						}
						else
						{
							connection_i.Send (autoLogin.send.Value + "\n");
						}
						
						// Inform the user that another autologin prompt has been found.
						buffer_i.Append ("done\n");
						
						if (iNextAutoLoginIndex_i < autoLoginDetails_i.Length)
						{
							// Print the next autologin pattern (if there is one)
							autoLogin = autoLoginDetails_i[iNextAutoLoginIndex_i];
							buffer_i.Append ("Waiting for AutoLogin prompt: " + autoLogin.pattern + "...");
						}
						
						// Reset the buffer we're using to keep track of all data since the last
						// time we recognised an autologin parameter.
						sBufferSinceLastAutoLogin_i = "";
					}
					else
					{
						LogDebug("No match for autologin pattern <" + autoLogin.pattern + "> in text of length " + sBufferSinceLastAutoLogin_i.Length);						
					}
				}
				else
				{
					// Run the tail command
					System.Threading.Thread.Sleep(500);
					LogDebug("Sending command: <" + sTelnetCommand_i + sPath_i + ">");
					connection_i.Send (sTelnetCommand_i + sPath_i + "\n");
					bExecuting_i = true;					
				}
			}
			else
			{				
				buffer_i.Append (e.Text);
			}
		}
		
		
		
		private void LogDebug(string sText_p)
		{
			Log.Debug(sDebugIdentifier_i + sText_p);
		}
		
		
		private string CreateDebugIdentifier(string sHost_p)
		{
			return "(Telnet: " + sHost_p + " #" + iConnectionCounter_c++ + ") ";			
		}

	}
}
